#include "../../Epsilon/file.h"
#include "../../Epsilon/strings.h"
#include "../../Epsilon/Epsilon3D/image.h"
#include "../../Epsilon/Epsilon3D/rendercontext.h"

#include "states.h"
#include "constants.h" // Things like NO_SOUND, etc...
#include "../console.h"

#include "../log_file.h"

eTB_LogFile effect_log ("logs/effect.log");

extern int GameError (int reason);

#include "effect.h"

/* On Win32, this is defined for us... */
#ifndef _INC_MMSYSTEM
typedef struct { 
    WORD  wFormatTag; 
    WORD  nChannels; 
    DWORD nSamplesPerSec; 
    DWORD nAvgBytesPerSec; 
    WORD  nBlockAlign; 
    WORD  wBitsPerSample; 
    WORD  cbSize; 
} WAVEFORMATEX; 
#endif

//
// 06/19/2011 - If defined, load resources the original UL client
//                required at all times on initial load...
//
//              cli_parse is designed to implement demand-loading,
//                so pre-caching is generally a bad idea.
//
//#define PRECACHE_RESOURCES


namespace Underlight
{
  EffectManager  fx_manager;
  EffectManager* effects = &fx_manager;
};

Underlight::EffectManager::~EffectManager (void)
{
  if (file != NULL) {
    delete file;
    file = NULL;
  }
}

// Generic dump, for unknown data where lexical analysis is needed.
static void dump (void* data, const unsigned int& len)
{
  eTB_Printf ("Size:  %d\n", len);

  if (len > 0) {
    unsigned width = 16;
    char *str = (char *)data;
    unsigned int j, i = 0;

    while (i < len) {
      eTB_Printf (" ");

      for (j = 0; j < width; j++) {
        if (i + j < len)
          eTB_Printf ("%02x ", (unsigned char) str [j]);
        else
          eTB_Printf ("   ");

        if ((j + 1) % (width / 2) == 0)
          eTB_Printf (" -  ");
      }

      for (j = 0; j < width; j++) {
        if (i + j < len)
          eTB_Printf ("%c", isprint (str [j]) ? str [j] : '.');
        else
          eTB_Printf (" ");
       }

       str += width;
       i += j;

      eTB_Printf ("\n");
    }
  }
}




bool
Underlight::EffectManager::LoadEffectFile (const TCHAR* filename)
{
  static bool done = false;
  
  // We only need to do this once...
  if (done)
    return true;

  // Open effect file
  file = new eDiskFile ();
////  file = new eMemoryMappedFile ();

  /// XXX: Stuck in ANSI mode, FIXME
  eStringA _filename (filename);
  if (! file->open (_filename, _T ("rb")))
  {
    GAME_ERROR (IDS_NO_OPEN_FFX_FILE);
    return false;
  }

  EffectFileHeader file_header;

  // Read effect file header
  if ((file->read (&file_header, sizeof (file_header), 1)) != 1)
  {
    GAME_ERROR (IDS_NO_READ_FFX_HEADER);
    return false;
  }

  num_palettes       = file_header.num_palettes;
  num_visual_effects = file_header.num_visual_effects;
  num_sound_effects  = file_header.num_sound_effects;

  // Load Palette Headers
  palette_headers             =
    new PaletteHeader [num_palettes];

  eFileSize num_palettes_read =
    file->read (palette_headers, sizeof (PaletteHeader), num_palettes);

  if (num_palettes_read != (UINT)num_palettes)
  {
    //eTB_Printf("num palettes: %d\n",num_palettes);
    GAME_ERROR (IDS_NO_READ_PALETTE_HEADERS);
    return false;
  }

  this->LoadPermanentPalettes ();


  // Load Visual Effect Headers - 1st seek to end of palette information
  const eFileOffset vfx_seek_pos = 
    (palette_headers [num_palettes - 1].file_position + PALETTE_SIZE);

  if (! file->seek (vfx_seek_pos, eFileSeekOp::Set))
  {
    //eTB_Printf("num palettes: %d\n",num_palettes);
    GAME_ERROR (IDS_NO_SEEK_TO_READ_FFX_HEADERS);
    return false;
  }

  visual_effect_headers =
    new VisualEffectHeader [num_visual_effects];

  eFileSize num_visual_effects_read =
    file->read ( visual_effect_headers,
                   sizeof (VisualEffectHeader),
                     num_visual_effects);

  if (num_visual_effects_read != (UINT)num_visual_effects)
  {
    //eTB_Printf("num visual_effects: %d\n",num_visual_effects);
    GAME_ERROR (IDS_NO_READ_VISUAL_FFX_HEADERS);
    return false;
  }

  // Temporary solution... we do not know enough to colorize
  //   things like avatars (yet)...
  for (int i = 0; i < num_visual_effects; i++) {
    if (visual_effect_headers [i].palette < 0)
      visual_effect_headers [i].palette = 0;
  }

  // Clear the bitmap list.
  memset (bitmap_info, 0, sizeof (BITMAPINFO_4DX) * MAX_BITMAPS);

  this->LoadPermanentVisualEffects ();

  file->seek ( visual_effect_headers [num_visual_effects - 1].file_position +
              (visual_effect_headers [num_visual_effects - 1].height *
               visual_effect_headers [num_visual_effects - 1].width  *
               visual_effect_headers [num_visual_effects - 1].frames *
               visual_effect_headers [num_visual_effects - 1].views  *
               visual_effect_headers [num_visual_effects - 1].bpp),
                 eFileSeekOp::Set);

  sound_effect_headers =
    new SoundEffectHeader [num_sound_effects];

  eFileSize sound_effects_read =
    file->read ( sound_effect_headers,
                   sizeof (SoundEffectHeader),
                     num_sound_effects );

  if (sound_effects_read != num_sound_effects)
  {
    //eTB_Printf("num_sound_effects: %d\n",num_sound_effects);
    GAME_ERROR (IDS_NO_READ_SOUND_FFX_HEADERS);
    return false;
  }

  // Clear (make sure it is completely zero'd) the sound list.
  memset (sounds, 0, sizeof (Sound) * MAX_SOUNDS);

  for (int i = 0; i < num_sound_effects; i++)
  {
//    eTB_Printf ("Sound Effect: %d - Count: %d,  Format: %d,  Data: %d\n", sound_effect_headers [i].id,
//     sound_effect_headers [i].count, sound_effect_headers [i].format_length, sound_effect_headers [i].data_length);

//     break;
  }

  this->LoadPermanentSoundEffects ();
  
  return true;
}

void
Underlight::EffectManager::LoadPermanentPalettes (void)
{
  for (int i = 0; i < num_palettes; i++)
  {
#ifndef PRECACHE_RESOURCES
    if (palette_headers [i].count) {
      palette_headers [i].count = 0;
    }
#else
    if (palette_headers [i].count) {
      this->LoadPalette (palette_headers [i].id);
    }
#endif
  }
}

int
Underlight::EffectManager::LoadEffectPalette (Underlight::id_t palette_id)
{
  PaletteHeader* palette_header = GetPaletteHeader (palette_id);

  if (palette_header != NULL) {
    // Allocate and load, if needed
    if (! (palette_header->count)) {
      num_active_palettes++;
      /*
      if (num_active_palettes >= MAX_ACTIVE_PALETTES)
      {
        GAME_ERROR (IDS_LOAD_TOO_MANY_PALETTES);
        return NO_PALETTE;
      }
      */

      this->LoadPalette (palette_id);
    }

    palette_header->count++;
    return palette_id;
  } else {
    /*
    LoadString(hInstance, IDS_EFFECT_ERR2, temp_message, sizeof(temp_message));
    _stprintf(errbuf, temp_message, palette_id, caller);
    NONFATAL_ERROR(errbuf);
    */

    return NO_PALETTE;
  }
}

void
Underlight::EffectManager::FreeEffectPalette (Underlight::id_t palette_id)
{
  for (int i=0; i<num_palettes; i++)
  {
    if (palette_headers[i].id == palette_id)
    {
      palette_headers [i].count--;

      if (! (palette_headers[i].count))
      {
        num_active_palettes--;
        /*
        shader->FreePalette(palette_id);
        */
      }

      return;
    }
  }

  ////////NONFATAL_ERROR (IDS_EFFECT_ERR3);
  return;
}

/// Load all of the visual effects whose reference count is ALREADY non-zero.
void
Underlight::EffectManager::LoadPermanentVisualEffects (void)
{
  for (int i = 0; i < num_visual_effects; i++)
  {
    if (visual_effect_headers [i].count) {
#ifndef PRECACHE_RESOURCES
      visual_effect_headers [i].count = 0;
#else
      this->LoadVisualEffect (visual_effect_headers [i].id, false);

      // LoadVisualEffect will load all of the texture frames and views.
      //
      //  * Skip the frames and views.
      i += (visual_effect_headers [i].frames *
            visual_effect_headers [i].views) - 1;
#endif
    }
  }
}

int
Underlight::EffectManager::LoadSkyBitmap (Underlight::id_t effect_id)
{
  VisualEffectHeader* visual_effect = GetBitmapHeader (effect_id);

  bool success = false;

  if (visual_effect != NULL) {
    success = true;

    visual_effect->palette = effect_id;

    if (! visual_effect->count) {
      success = this->LoadVisualEffect (effect_id, true);

      visual_effect->count++;
    }
  }

  if (success || effect_id == 0) // Sky 0 is a special case...
    return effect_id;
  else {
    //effect_log.Status (0, _T ("Could not load sky bitmap: %d\n"), effect_id);
    eTB_Printf (_T ("Could not load sky bitmap: %d\n"), effect_id);
    /*
    LoadString (hInstance, IDS_EFFECT_ERR4, temp_message, sizeof (temp_message));
    _stprintf  (errbuf, temp_message, effect_id, caller);
    NONFATAL_ERROR (errbuf);
    */
    return NO_BITMAP;
  }
}

int
Underlight::EffectManager::LoadEffectBitmaps (Underlight::id_t effect_id)
{
  VisualEffectHeader* visual_effect = GetBitmapHeader (effect_id);

  bool success = false;

  if (visual_effect != NULL) {
    success = true;

    if (! (visual_effect->count)) {
      success = this->LoadVisualEffect (effect_id, false);

      visual_effect->count++;
    }
  }

  if (success)
    return effect_id;
  else {
    eTB_Printf (_T ("Could not load effect bitmap: %d\n"), effect_id);
    /*
    LoadString (hInstance, IDS_EFFECT_ERR4, temp_message, sizeof (temp_message));
    _stprintf  (errbuf, temp_message, effect_id, caller);
    NONFATAL_ERROR (errbuf);
    */
    return NO_BITMAP;
  }
}

void
Underlight::EffectManager::FreeEffectBitmaps (Underlight::id_t effect_id)
{
  VisualEffectHeader* visual_effect = GetBitmapHeader (effect_id);

  if (visual_effect)
  {
    // Deallocate the RAM, if needed (count == 0)
    visual_effect->count--;

    if (! (visual_effect->count))
    {
      visual_effect_bytes -= visual_effect->size;

      for (int i = 0; i < (visual_effect->frames * visual_effect->views); i++) {
        BITMAPINFO_4DX* bitmap = GetBitmap (effect_id, i);

        if (bitmap && bitmap->image)
          delete bitmap->image;
      }
    }

    return;
  }

  /////////NONFATAL_ERROR (IDS_EFFECT_ERR8);
}

int
Underlight::EffectManager::LoadSoundEffect (Underlight::id_t sound_id)
{
  for (int i = 0; i < num_sound_effects; i++)
  {
    if (sound_effect_headers [i].id == sound_id)
    {
      // Allocate and load, if needed
      if (! (sound_effect_headers [i].count))
        this->LoadSound (i, sound_id);

      sound_effect_headers [i].count++;

      return sound_id;
    }
  }

  return NO_SOUND;
}

void
Underlight::EffectManager::FreeSoundEffect (Underlight::id_t sound_id)
{
  for (int i = 0; i < num_sound_effects; i++)
  {
    if (sound_effect_headers [i].id == sound_id)
    {
      sound_effect_headers [i].count--;

      if (! (sound_effect_headers [i].count))
      {
        sound_effect_bytes -= sound_effect_headers [i].data_length;
        delete [] sounds [i].data;
        sounds [i].data = NULL;
      }

      return;
    }
  }
}

void
Underlight::EffectManager::LoadSound (int index, Underlight::id_t sound_id)
{
  int          id,        count;
  unsigned int data_size, format_size;

  format_size = sound_effect_headers [index].format_length;
  data_size   = sound_effect_headers [index].data_length;
  id          = sound_effect_headers [index].id;

  if (data_size < 655360) {
     file->seek (sound_effect_headers [index].file_position, eFileSeekOp::Set);

     char* format = new char [format_size];
     file->read (format, format_size, 1);

     WAVEFORMATEX* pFormat = ((WAVEFORMATEX *)format);

/*
     eTB_Printf ("Format: %x, Number of Channels: %d, Samples Per Second: %d, %d-Bit\n",
         ((WAVEFORMATEX*)format)->wFormatTag,
         ((WAVEFORMATEX*)format)->nChannels,
         ((WAVEFORMATEX*)format)->nSamplesPerSec,
         ((WAVEFORMATEX*)format)->wBitsPerSample);
*/

    sounds [sound_id].data            = new unsigned char [data_size];
    sounds [sound_id].sample_rate     = pFormat->nSamplesPerSec;
    sounds [sound_id].bits_per_sample = pFormat->wBitsPerSample;
    sounds [sound_id].num_channels    = pFormat->nChannels;

    unsigned char* ptr = sounds [sound_id].data;

    sound_effect_bytes += sound_effect_headers [index].data_length;

    file->read (ptr, data_size, 1);

    delete [] format; /* 5/28/2011 - This was being done way too soon, causing
                                       unpredictable memory corruption! */
  } else {
    eTB_Printf ("Invalid Sound: %d [%d]\n", index, sound_id);
  }
}

void
Underlight::EffectManager::LoadPermanentSoundEffects (void)
{
  for (int i = 0; i < num_sound_effects; i++)
  {
    if (sound_effect_headers [i].count) {
#ifndef PRECACHE_RESOURCES
      sound_effect_headers [i].count = 0;
#else
      this->LoadSound (i, sound_effect_headers [i].id);
#endif
    }
  }
}

/// Decode RLE data up to max_len (supports runs up to 255 bytes long)
void
RLE_Decode (unsigned char* data, unsigned char* out, int max_len, int* out_len)
{
  *out_len = 0;

  unsigned char* src = data;
  unsigned char* dst = out;

  for (int i = 0; i < max_len; i++) {
    unsigned char span_len  = *src++;
    unsigned char span_data = *src++;

    memset (dst, span_data, span_len);
    *out_len += span_len;

    if (*out_len == max_len) {
      return;
    } else if (*out_len > max_len) {
      eTB_Printf ("RLE_Decode: Decode Error (Too Many Bytes)\n");
      return;
    }

    dst += span_len;
  }
}

/// Rotate the image 270 degrees
static unsigned char*
Unrotate (unsigned char* data, unsigned char* dest, short width, short height)
{
  unsigned char* d   = dest;
  unsigned char* tmp = new unsigned char [width * height];

  #define TEXEL_OUT(x,y) (((y) * width) + ((x)))
  #define TEXEL_IN(x,y)  (((y) * height) + ((x)))

  for (int y = 0; y < height; y++) {
    for (int x = 0; x < width; x++) {
      tmp [width * height - TEXEL_OUT (x,y) - 1] = data [TEXEL_IN (y,x)];
    }
  }

  memcpy (dest, tmp, width * height);

  delete [] tmp;

#undef TEXEL

  extern void swap_columns   (unsigned char* data, int width, int height, int bpp);

  swap_columns (dest, width, height, 1);

  return dest;
}

/// Flip the image vertically
unsigned char*
FlipV (unsigned char* data, unsigned char* dest, short width, short height, int bpp)
{
  if (data != dest)
    memcpy (dest, data, width * height);

  extern void swap_scanlines (unsigned char* data, int width, int height, int bpp);
  swap_scanlines (dest, width, height, bpp);

  return dest;
}

#include "util.h" // SaveTGAFile
#include "../../Epsilon/Epsilon3D/image.h"

extern unsigned char*
PalettedImageToRGB24 (unsigned char* data, unsigned char* dest, short width, short height, Underlight::Palette* palette);

extern unsigned char*
ProcessPalettedImage ( unsigned char*       data,
                       unsigned char*       dest,
                       short                width,
                       short                height,
                       Underlight::Palette* palette,
                       int                  bpp );


bool
Underlight::EffectManager::LoadVisualEffect (Underlight::id_t effect_id, bool sky)
{
  int           i,      j,
                views,  frames,
                height, width,
                bpp,    rle,
                palette_id;

  int           total_size,
                bm_index     = effect_id;

  unsigned char *effect_bits = NULL,
                *dst,
                *src;

  int           pixels,
                frame_size;
  size_t        count;

  VisualEffectHeader* visual_effect = GetBitmapHeader (effect_id);
  VisualEffectHeader* header        = visual_effect;

  if (! (visual_effect && header)) {
    GAME_ERROR (IDS_NO_READ_VISUAL_FFX_HEADERS);
    return false;
  }

  views      = visual_effect->views;
  frames     = visual_effect->frames;
  height     = visual_effect->height;
  width      = visual_effect->width;
  bpp        = visual_effect->bpp;
  palette_id = visual_effect->palette;
  rle        = visual_effect->rle;
  total_size = visual_effect->size;

  if (bpp != 1)
    palette_id = -4;

  // 06/19/2011 - Special Case:  SKY --> Palette = Effect ID
  if (width == 800 && height == 300) {
    visual_effect->palette = effect_id;
    palette_id = effect_id;
  }

    // Ignore special-case palettes
  if (visual_effect->palette >= 0) {
    this->LoadEffectPalette (visual_effect->palette);

    /*
    eTB_Printf (_T ("Bitmap: %d\n"), effect_id);
    eTB_Printf (_T (" >> (%d x %d : %d bpp [%d Frames * %d Views]) Palette: %d, RLE: %s, Size: %d\n"),
      header->width,    header->height, header->bpp * 8,
        header->frames, header->views,
          header->palette,
            (header->rle ? _T ("Yes") : _T ("No")),
              header->size);
    */
  } else {
    // Palette IDs: -1 and -2 are really color tables.
#if 0
    visual_effect->count = 0;
    return false;
#else
    // Temporary solution... we do not know enough to colorize
    //   things like avatars (yet)...
    visual_effect->palette = 0;
    this->LoadEffectPalette (visual_effect->palette);
#endif
  }

  effect_bits = new unsigned char [total_size];

  //eTB_Printf("seeking to %d...\n",visual_effect_headers[index].file_position);
  if (! file->seek (visual_effect->file_position, eFileSeekOp::Set))
  {
    GAME_ERROR (IDS_NO_SEEK_TO_FIND_VISUAL_FFX);
    return false;
  }

  // Read the bits for this entire chunk
  count = file->read (effect_bits, total_size, 1);
  if (count != 1)
  {
    /*
    LoadString(hInstance, IDS_EFFECT_ERR1, temp_message, sizeof(temp_message));
    _stprintf(errbuf, temp_message, (height * width * views * frames), index, bm_index, height, width, views, frames);
    DebugOut(_T("%s"), errbuf);
    */
    GAME_ERROR (IDS_NO_READ_VISUAL_FFX_BITS);
    return false;
  }

  pixels = total_size / bpp;

  src = effect_bits;
  for (j = 0; j < frames * views; j++)
  {
    unsigned char* paletted = NULL;
    eImage*        img      = NULL;

    const eImagePixelFormat    format  = eImagePixelFormats::RGB;
    const Underlight::Palette* palette = effects->GetPalette (palette_id);

    // set dst as the appropriate offset into the effect bitmap space
    if (rle)
    {
      int size = *((int *)src);  // Get size of compressed frame
      src += sizeof (int);       // Move onto real data

      size *= bpp;

      frame_size = size;

      unsigned char* rle_data = new unsigned char [frame_size];
      paletted                = new unsigned char [width * height];

      memcpy (rle_data, src, size);

      short* columns = (short *)rle_data;

      unsigned char* rle_dest  = paletted;
      unsigned char  clip_mask = NULL;
      bool           masked    = false;

      int   rle_decode_len;

      // Decode the scanlines
      for (i = 0; i < width; i++) {
        // Skip transparent scanlines
        if (columns [i] == 0) {
          memset (rle_dest, 0x00, height);
          rle_dest += height;
          continue;
        }

        unsigned char* const
          rle_src = (unsigned char *)rle_data + columns [i] + 2;

        // First 2 bytes of each scanline are for clipping,
        //  we can safely ignore them.
        RLE_Decode (rle_src, rle_dest, height, &rle_decode_len);

        unsigned char* clip     = (rle_data + columns [i]);
        unsigned char  max_clip = *(clip + 1);
        unsigned char  min_clip = *clip;

#define FULL_CLIPPING
#ifdef FULL_CLIPPING
//#define DEBUG_CLIPPING
#ifdef DEBUG_CLIPPING
        //
        // Debug Clipping Info
        //

        //clip_mask = *((rle_dest + min_clip) + (height - min_clip) - 1);
        //eTB_Printf ("Min / Max Clipping: %d : %d\n", min_clip, max_clip);

        //memset (rle_dest + min_clip, 0xff, height - min_clip);
        //memset (rle_dest + min_clip, 0xaa, max_clip - min_clip);
        //memset (rle_dest + max_clip, 0x44, height - max_clip);
#else
        if (masked == false) {
          //clip_mask = *((rle_dest + min_clip) + (height - min_clip) - 1);
          clip_mask = *((rle_dest + max_clip) + (height - max_clip) - 1);
          masked = true;
        }

        for (int i = 0; i < height; i++) {
          if (rle_dest [i] == clip_mask)
            rle_dest [i] = 0x00;
        }
#endif
#endif

        Assert (rle_decode_len == height,
                   _T ("Invalid RLE Decode Length!"));

        rle_dest += rle_decode_len;
      }

      delete [] rle_data;

      if (bpp == 1)
        img = new ePalettedImage (width, height, format, 1, paletted,
                                    palette->color, true);
      else
        img = new eImage         (width, height, format, 2, paletted, true);

      // RLE encoded Underlight textures are pre-rotated so that scanline
      //  skipping is more efficient.
      Unrotate    (paletted, paletted,
                     width,  height);
    }

    else
    {
      frame_size = height * width * bpp;

      paletted = new unsigned char [frame_size];

      memcpy (paletted, src, frame_size);

      if (bpp == 1)
        img = new ePalettedImage (width, height, format, 1, paletted,
                                    palette->color, true);
      else
        img = new eImage         (width, height, format, 2, paletted, true);

      FlipV  (paletted, paletted,
                 width, height, bpp);
    }

    if (sky) {
//      extern void swap_columns_ex (unsigned char* data, int width, int height, int bpp, int x_off, int y_off);
//      swap_columns_ex (paletted, 800, 300, 1, 0,   160);
      //swap_columns_ex (paletted, 800, 300, 1, 160, 160);
//      swap_columns_ex (paletted, 800, 300, 1, 320, 160);
//      swap_columns_ex (paletted, 800, 300, 1, 640, 160);
    }

    dst = NULL;

    if (palette_id >= 0) {
      eImage* unpaletted = img->Convert (eImagePixelFormats::RGB, 3, &dst);

//#define KEEP_PALETTED
#ifndef KEEP_PALETTED
      delete img;

#define GEN_ALPHA_MASK
#ifdef GEN_ALPHA_MASK
#ifdef HAS_D3D
      // Only swap the color channels when using Direct3D.
      if (e3dDirect3D::IsDevicePresent ())
        unpaletted->Convert (eImagePixelFormats::BGR, 3);
#endif

      eImage* rgba = unpaletted->Convert (eImagePixelFormats::RGBA, 4);
      delete unpaletted;

      img = rgba;
#else
      img = unpaletted;
#endif
#endif
    }

    if (bpp == 2) {
      short* pixels = (short *)img->Data ();
      short* pixel  = pixels;

      byte* data = new byte [img->Width () * img->Height () * 3];

      for (int i = 0; i < (img->Width () * img->Height ()); i++) {
        data [(i * 3)]     = (byte)((*pixel & 0xF800) >> 8);
        data [(i * 3) + 1] = (byte)((*pixel & 0x07E0) >> 3);
        data [(i * 3) + 2] = (byte)((*pixel & 0x001F) << 3);

        pixel = &((short *)img->Data ())[i + 1];
      }

      eImage* rgb = new eImage (img->Width (), img->Height (), eImagePixelFormats::RGB, 3, data, true);

      delete img;

#ifdef GEN_ALPHA_MASK
      eImage* rgba = rgb->Convert (eImagePixelFormats::RGBA, 4);
      delete rgb;
      img = rgba;
#else
      img = rgb;
#endif

    }

    bitmap_info [bm_index].image = img;

    /*
    char szName [MAX_PATH];

    snprintf (szName, MAX_PATH, "Effects/Effect-%d-Frame-%d.tga", effect_id, j);
    SaveTGAFile (szName, width, height, (bpp == 1) ? 24 : 16, dst);
    */

    src += frame_size;

    ////bitmap_info [bm_index].image   = dst;
    bitmap_info [bm_index].h       = width;
    bitmap_info [bm_index].w       = height;
    bitmap_info [bm_index].palette = palette_id; // -1 for color tables

    visual_effect_bytes += (width * height * img->Bpp ());

    //eTB_Printf (_T (" >>> Frame: %d  (%d x %d) Palette: %d, Address: %p\n"), j, height, width, palette, dst);

    bm_index++;
  }

  delete [] effect_bits;

  return true;
}

void
Underlight::EffectManager::LoadPalette (Underlight::id_t palette_id)
{
  if ((palette_id == MAGIC_AVATAR_PALETTE_2) || (palette_id == MAGIC_AGENT_PALETTE_2)) 
    return; // Don't load 2nd part of magic palettes

  PaletteHeader* palette_header = GetPaletteHeader (palette_id);

  if (! file->seek (palette_header->file_position, eFileSeekOp::Set))
  {
    GAME_ERROR (IDS_NO_SEEK_TO_FIND_PALETTE);
    return;
  }

/*
  if (effect_id == MAGIC_AVATAR_PALETTE_1)
  { // read two magic palettes & combine into one
    spal = new unsigned char[PALETTE_SIZE*2];
    if ((checksum_fread(spal, PALETTE_SIZE, 1, fh)) != 1)
    {
      GAME_ERROR(IDS_NO_READ_PALETTE);
      delete spal;
      return;
    } // note - part 2 MUST be right after part 1
    if (fseek(fh, palette_headers[index+1].file_position, SEEK_SET))
    {
      GAME_ERROR(IDS_NO_SEEK_TO_FIND_PALETTE);
      return;
    }
    if ((checksum_fread((spal + PALETTE_SIZE), PALETTE_SIZE, 1, fh)) != 1)
    {
      GAME_ERROR(IDS_NO_READ_PALETTE);
      delete spal;
      return;
    } // note - part 2 MUST be right after part 1
    // add double size palette
     shader->AddPalette(spal, effect_id, PALETTE_SIZE*2);
  }
  else if (effect_id == MAGIC_AGENT_PALETTE_1)
  { // read two magic palettes & combine into one
    spal = new unsigned char[PALETTE_SIZE*2];
    if ((checksum_fread(spal, PALETTE_SIZE, 1, fh)) != 1)
    {
      GAME_ERROR(IDS_NO_READ_PALETTE);
      delete spal;
      return;
    } // note - part 2 MUST be right after part 1
    if (fseek(fh, palette_headers[index+1].file_position, SEEK_SET))
    {
      GAME_ERROR(IDS_NO_SEEK_TO_FIND_PALETTE);
      return;
    }
    if ((checksum_fread((spal + PALETTE_SIZE), PALETTE_SIZE, 1, fh)) != 1)
    {
      GAME_ERROR(IDS_NO_READ_PALETTE);
      delete spal;
      return;
    } // note - part 2 MUST be right after part 1
    // add double size palette
     shader->AddPalette(spal, effect_id, PALETTE_SIZE*2);
  }
  else
  */
  { // Reading palette of normal size
    //eTB_Printf("reading visual effect %d\n",i);
    if ((file->read (&palettes [palette_id], PALETTE_SIZE, 1)) != 1)
    {
      GAME_ERROR (IDS_NO_READ_PALETTE);
      return;
    }
  }

  return;
}

Underlight::VisualEffectHeader*
Underlight::EffectManager::GetBitmapHeader (Underlight::id_t effect_id)
{
  for (int i = 0; i < num_visual_effects; i++) {
    if (visual_effect_headers [i].id == effect_id) {
      return &visual_effect_headers [i];
    }
  }

  return NULL;
}

Underlight::PaletteHeader*
Underlight::EffectManager::GetPaletteHeader (Underlight::id_t palette_id)
{
  for (int i = 0; i < num_palettes; i++) {
    if (palette_headers [i].id == palette_id) {
      return &palette_headers [i];
    }
  }

  return NULL;
}

Underlight::Palette*
Underlight::EffectManager::GetPalette (Underlight::id_t palette_id)
{
  return &palettes [palette_id];
}

BITMAPINFO_4DX*
Underlight::EffectManager::GetBitmap (Underlight::id_t effect_id, int frame)
{
  return &bitmap_info [effect_id + frame];
}

Underlight::SoundEffectHeader*
Underlight::EffectManager::GetSoundHeader (Underlight::id_t sound_id)
{
  for (int i = 0; i < num_sound_effects; i++) {
    if (sound_effect_headers [i].id == sound_id) {
      return &sound_effect_headers [i];
    }
  }

  return NULL;
}

Underlight::Sound*
Underlight::EffectManager::GetSound (Underlight::id_t sound_id)
{
  /* Demand-Loading. */
  if (sounds [sound_id].data == NULL)
    this->LoadSoundEffect (sound_id);

  return &sounds [sound_id];
}
